package com.myimooc.shiro.web.config;

import com.myimooc.shiro.web.cache.RedisCacheManager;
import com.myimooc.shiro.web.filter.RolesOrFilter;
import com.myimooc.shiro.web.realm.CustomRealm;
import com.myimooc.shiro.web.session.CustomSessionManager;
import com.myimooc.shiro.web.session.RedisSessionDao;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import static com.myimooc.shiro.web.constant.Constants.Role.NUM20000000;

/**
 * @className: ShiroConfig
 * @description: 配置Shiro
 * @date: 2020/5/12
 * @author: cakin
 */
@Configuration
public class ShiroConfig {

    /**
     * 功能描述：注入simpleCookie
     *
     * @author cakin
     * @date 2020/5/13
     * @return SimpleCookie
     */
    @Bean
    public SimpleCookie simpleCookie() {
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        // 存活时间（单位：秒）
        simpleCookie.setMaxAge(NUM20000000);
        return simpleCookie;
    }

    /**
     * 功能描述：配置CookieRememberMeManager
     *
     * @author cakin
     * @date 2020/5/13
     * @return CookieRememberMeManager
     */
    @Bean
    public CookieRememberMeManager cookieRememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        // 注入simpleCookie
        cookieRememberMeManager.setCookie(simpleCookie());
        return cookieRememberMeManager;
    }

    /**
     * 功能描述：注入cacheManager
     *
     * @author cakin
     * @date 2020/5/13
     * @return CacheManager
     */
    @Bean
    public CacheManager cacheManager() {
        return new RedisCacheManager();
    }

    /**
     * 功能描述：shiroFilterFactoryBean的配置
     *
     * @return ShiroFilterFactoryBean
     * @author cakin
     * @date 2020/5/12
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置 securityManager的bean
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        // 登录页，如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("login");
        // 未认证的跳转页面
        shiroFilterFactoryBean.setUnauthorizedUrl("403");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 过滤链定义
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        /**
         * Shiro的内置过滤器
         * -----------------
         * 根认证相关的过滤器
         * anon:不需要认证
         * authBasic：当一个客户端向一个需要认证的HTTP服务器进行数据请求时，如果之前没有认证过，HTTP服务器会返回401状态码，
         * 要求客户端输入用户名和密码。用户输入用户名和密码后，用户名和密码会经过BASE64加密附加到请求信息中再次请求HTTP服务器，
         * HTTP服务器会根据请求头携带的认证信息，决定是否认证成功及做出相应的响应。
         * 参考：https://www.cnblogs.com/rinack/p/7595232.html
         * anthc：需要认证后才能访问
         * user：需要当前存在用户才能进行访问
         * logout：退出
         * ------------------
         * 跟授权相关的过滤器
         * perms：  perms["user:delete","user:update"] 具备相关的权限才能访问
         * roles：  roles["admin"] 具备相关的角色才能访问
         * ssl：要求是安全协议 https
         * port：要求端口是中括号中列出的端口
         */
        // 配置不会被拦截的链接 顺序判断，从上到下依次匹配，匹配上了就返回了
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/subLogin", "anon");
        filterChainDefinitionMap.put("/testRole1", "rolesOr[admin,admin1]"); // admin可以不加双引号，这个是自定义过滤器
        filterChainDefinitionMap.put("/testRole", "roles[\"admin\"]"); // 同时具备 // admin也可以加双引号，但不能加单引号
        filterChainDefinitionMap.put("/testPerms", "perms[\"user:delete\"]"); // 如果该用户的在数据库中的权限跟这里定义的匹配，才能通过鉴权
        // 用户在数据库要同时具备这两个权限，才能鉴权成功；如果数据库中的数据只是部分匹配，也会鉴权失败
        filterChainDefinitionMap.put("/testPerms1", "perms[\"user:delete\",\"user:update\"]");
        filterChainDefinitionMap.put("/logout", "logout");
        //<!-- 过滤链定义，从上向下顺序执行，一般将/**放在最为下边 -->:这是一个坑呢，一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都可以匿名访问-->
        filterChainDefinitionMap.put("/*", "authc"); // 这种是需要认证的，一般放在最下面
        // 配置拦截链
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        // 注册自定义Filter
        Map<String, Filter> filters = new HashMap<>();
        filters.put("rolesOr", rolesOrFilter());
        shiroFilterFactoryBean.setFilters(filters);

        return shiroFilterFactoryBean;
    }

    /**
     * 功能描述：配置sessionManager
     *
     * @return CustomSessionManager
     * @author cakin
     * @date 2020/5/12
     * @description: 需要注入SessionDAO
     */
    @Bean(value = "sessionManager")
    public CustomSessionManager sessionManager() {
        CustomSessionManager sessionManager = new CustomSessionManager();
        sessionManager.setSessionDAO(redisSessionDao());
        return sessionManager;
    }

    /**
     * 功能描述：配置SessionDao
     *
     * @return RedisSessionDao
     * @author cakin
     * @date 2020/5/12
     */
    @Bean
    public RedisSessionDao redisSessionDao() {
        return new RedisSessionDao();
    }

    /**
     * 功能描述：配置角色过滤器
     *
     * @return RolesOrFilter
     * @author cakin
     * @date 2020/5/12
     */
    @Bean
    public RolesOrFilter rolesOrFilter() {
        return new RolesOrFilter();
    }

    /**
     * 功能描述：配置权限的注解功能
     *
     * @param securityManager 安全管理器
     * @return AuthorizationAttributeSourceAdvisor
     * @author cakin
     * @date 2020/5/12
     * @description:
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( org.apache.shiro.mgt.SecurityManager securityManager ) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 功能描述：shiro的生命周期后处理器
     *
     * @return LifecycleBeanPostProcessor
     * @author cakin
     * @date 2020/5/12
     * @description: LifecycleBeanPostProcessor 实现了 BeanPostProcessor 接口。
     * 在 LifecycleBeanPostProcessor 类中实现了 DestructionAwareBeanPostProcessor，
     * 表示在bean销毁之前进行某种处理。
     * 原文链接：https://blog.csdn.net/huanglnquan/article/details/94329548
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 功能描述：配置自定义Realm
     *
     * @return CustomRealm
     * @author cakin
     * @date 2020/5/12
     */
    @Bean
    public CustomRealm customRealm() {
        return new CustomRealm();
    }

    /**
     * 功能描述：配置认证算法
     *
     * @return HashedCredentialsMatcher
     * @author cakin
     * @date 2020/5/12
     */
    @Bean
    public HashedCredentialsMatcher credentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("md5");
        matcher.setHashIterations(1);
        return matcher;
    }

    /**
     * 功能描述：配置securityManager
     *
     * @return DefaultWebSecurityManager
     * @author cakin
     * @date 2020/5/12
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm()); // 配置Realm
        // 配置缓存管理器，主要缓存角色数据和权限数据，减少对数据库压力，一般不做认证缓存
        securityManager.setCacheManager(cacheManager());
        securityManager.setRememberMeManager(cookieRememberMeManager()); // 配置cookie的记住我功能
        securityManager.setSessionManager(sessionManager()); // 配置session管理器
        return securityManager;
    }
}
